import { Util, Collection } from './Util';
import { Factory } from './Factory';
import { Node, NodeConfig } from './Node';
import {
  getNumberValidator,
  getNumberOrAutoValidator,
  getStringValidator,
  getBooleanValidator,
} from './Validators';

import { Context, SceneContext } from './Context';
import { _registerNode } from './Global';
import * as PointerEvents from './PointerEvents';

import { GetSet, Vector2d } from './types';
import { HitCanvas, SceneCanvas, Canvas } from './Canvas';
import { Container } from './Container';

// hack from here https://stackoverflow.com/questions/52667959/what-is-the-purpose-of-bivariancehack-in-typescript-types/52668133#52668133
export type ShapeConfigHandler<TTarget> = {
  bivarianceHack(ctx: Context, shape: TTarget): void;
}['bivarianceHack'];

export interface ShapeConfig extends NodeConfig {
  fill?: string;
  fillPatternImage?: HTMLImageElement;
  fillPatternX?: number;
  fillPatternY?: number;
  fillPatternOffset?: Vector2d;
  fillPatternOffsetX?: number;
  fillPatternOffsetY?: number;
  fillPatternScale?: Vector2d;
  fillPatternScaleX?: number;
  fillPatternScaleY?: number;
  fillPatternRotation?: number;
  fillPatternRepeat?: string;
  fillLinearGradientStartPoint?: Vector2d;
  fillLinearGradientStartPointX?: number;
  fillLinearGradientStartPointY?: number;
  fillLinearGradientEndPoint?: Vector2d;
  fillLinearGradientEndPointX?: number;
  fillLinearGradientEndPointY?: number;
  fillLinearGradientColorStops?: Array<number | string>;
  fillRadialGradientStartPoint?: Vector2d;
  fillRadialGradientStartPointX?: number;
  fillRadialGradientStartPointY?: number;
  fillRadialGradientEndPoint?: Vector2d;
  fillRadialGradientEndPointX?: number;
  fillRadialGradientEndPointY?: number;
  fillRadialGradientStartRadius?: number;
  fillRadialGradientEndRadius?: number;
  fillRadialGradientColorStops?: Array<number | string>;
  fillEnabled?: boolean;
  fillPriority?: string;
  stroke?: string;
  strokeWidth?: number;
  hitStrokeWidth?: number | string;
  strokeScaleEnabled?: boolean;
  strokeHitEnabled?: boolean;
  strokeEnabled?: boolean;
  lineJoin?: string;
  lineCap?: string;
  sceneFunc?: (con: Context, shape: Shape) => void;
  hitFunc?: (con: Context, shape: Shape) => void;
  shadowColor?: string;
  shadowBlur?: number;
  shadowOffset?: Vector2d;
  shadowOffsetX?: number;
  shadowOffsetY?: number;
  shadowOpacity?: number;
  shadowEnabled?: boolean;
  shadowForStrokeEnabled?: boolean;
  dash?: number[];
  dashOffset?: number;
  dashEnabled?: boolean;
  perfectDrawEnabled?: boolean;
}

var HAS_SHADOW = 'hasShadow';
var SHADOW_RGBA = 'shadowRGBA';
var patternImage = 'patternImage';
var linearGradient = 'linearGradient';
var radialGradient = 'radialGradient';

var dummyContext;
function getDummyContext(): CanvasRenderingContext2D {
  if (dummyContext) {
    return dummyContext;
  }
  dummyContext = Util.createCanvasElement().getContext('2d');
  return dummyContext;
}

export const shapes = {};

// TODO: idea - use only "remove" (or destroy method)
// how? on add, check that every inner shape has reference in konva store with color
// on remove - clear that reference
// the approach is good. But what if we want to cache the shape before we add it into the stage
// what color to use for hit test?

function _fillFunc(context) {
  context.fill();
}
function _strokeFunc(context) {
  context.stroke();
}
function _fillFuncHit(context) {
  context.fill();
}
function _strokeFuncHit(context) {
  context.stroke();
}

function _clearHasShadowCache() {
  this._clearCache(HAS_SHADOW);
}

function _clearGetShadowRGBACache() {
  this._clearCache(SHADOW_RGBA);
}

function _clearFillPatternCache() {
  this._clearCache(patternImage);
}

function _clearLinearGradientCache() {
  this._clearCache(linearGradient);
}

function _clearRadialGradientCache() {
  this._clearCache(radialGradient);
}

/**
 * Shape constructor.  Shapes are primitive objects such as rectangles,
 *  circles, text, lines, etc.
 * @constructor
 * @memberof Konva
 * @augments Konva.Node
 * @param {Object} config
 * @@shapeParams
 * @@nodeParams
 * @example
 * var customShape = new Konva.Shape({
 *   x: 5,
 *   y: 10,
 *   fill: 'red',
 *   // a Konva.Canvas renderer is passed into the sceneFunc function
 *   sceneFunc (context, shape) {
 *     context.beginPath();
 *     context.moveTo(200, 50);
 *     context.lineTo(420, 80);
 *     context.quadraticCurveTo(300, 100, 260, 170);
 *     context.closePath();
 *     // Konva specific method
 *     context.fillStrokeShape(shape);
 *   }
 *});
 */
export class Shape<Config extends ShapeConfig = ShapeConfig> extends Node<
  Config
> {
  _centroid: boolean;
  colorKey: string;

  _fillFunc: (ctx: Context) => void;
  _strokeFunc: (ctx: Context) => void;
  _fillFuncHit: (ctx: Context) => void;
  _strokeFuncHit: (ctx: Context) => void;

  constructor(config?: Config) {
    super(config);
    // set colorKey
    var key;

    while (true) {
      key = Util.getRandomColor();
      if (key && !(key in shapes)) {
        break;
      }
    }

    this.colorKey = key;
    shapes[key] = this;

    this.on(
      'shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva',
      _clearHasShadowCache
    );

    this.on(
      'shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva',
      _clearGetShadowRGBACache
    );

    this.on(
      'fillPriorityChange.konva fillPatternImageChange.konva fillPatternRepeatChange.konva fillPatternScaleXChange.konva fillPatternScaleYChange.konva',
      _clearFillPatternCache
    );

    this.on(
      'fillPriorityChange.konva fillLinearGradientColorStopsChange.konva fillLinearGradientStartPointXChange.konva fillLinearGradientStartPointYChange.konva fillLinearGradientEndPointXChange.konva fillLinearGradientEndPointYChange.konva',
      _clearLinearGradientCache
    );

    this.on(
      'fillPriorityChange.konva fillRadialGradientColorStopsChange.konva fillRadialGradientStartPointXChange.konva fillRadialGradientStartPointYChange.konva fillRadialGradientEndPointXChange.konva fillRadialGradientEndPointYChange.konva fillRadialGradientStartRadiusChange.konva fillRadialGradientEndRadiusChange.konva',
      _clearRadialGradientCache
    );
  }

  /**
   * get canvas context tied to the layer
   * @method
   * @name Konva.Shape#getContext
   * @returns {Konva.Context}
   */
  getContext() {
    return this.getLayer().getContext();
  }
  /**
   * get canvas renderer tied to the layer.  Note that this returns a canvas renderer, not a canvas element
   * @method
   * @name Konva.Shape#getCanvas
   * @returns {Konva.Canvas}
   */
  getCanvas() {
    return this.getLayer().getCanvas();
  }

  getSceneFunc() {
    return this.attrs.sceneFunc || this['_sceneFunc'];
  }

  getHitFunc() {
    return this.attrs.hitFunc || this['_hitFunc'];
  }
  /**
   * returns whether or not a shadow will be rendered
   * @method
   * @name Konva.Shape#hasShadow
   * @returns {Boolean}
   */
  hasShadow() {
    return this._getCache(HAS_SHADOW, this._hasShadow);
  }
  _hasShadow() {
    return (
      this.shadowEnabled() &&
      this.shadowOpacity() !== 0 &&
      !!(
        this.shadowColor() ||
        this.shadowBlur() ||
        this.shadowOffsetX() ||
        this.shadowOffsetY()
      )
    );
  }
  _getFillPattern() {
    return this._getCache(patternImage, this.__getFillPattern);
  }
  __getFillPattern() {
    if (this.fillPatternImage()) {
      var ctx = getDummyContext();
      const pattern = ctx.createPattern(
        this.fillPatternImage(),
        this.fillPatternRepeat() || 'repeat'
      );
      // TODO: how to enable it? It doesn't work in FF...
      // pattern.setTransform({
      //   a: this.fillPatternScaleX(), // Horizontal scaling. A value of 1 results in no scaling.
      //   b: 0, // Vertical skewing.
      //   c: 0, // Horizontal skewing.
      //   d: this.fillPatternScaleY(), // Vertical scaling. A value of 1 results in no scaling.
      //   e: 0, // Horizontal translation (moving).
      //   f: 0 // Vertical translation (moving).
      // });
      return pattern;
    }
  }
  _getLinearGradient() {
    return this._getCache(linearGradient, this.__getLinearGradient);
  }
  __getLinearGradient() {
    var colorStops = this.fillLinearGradientColorStops();
    if (colorStops) {
      var ctx = getDummyContext();

      var start = this.fillLinearGradientStartPoint();
      var end = this.fillLinearGradientEndPoint();
      var grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y);

      // build color stops
      for (var n = 0; n < colorStops.length; n += 2) {
        grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string);
      }
      return grd;
    }
  }

  _getRadialGradient() {
    return this._getCache(radialGradient, this.__getRadialGradient);
  }
  __getRadialGradient() {
    var colorStops = this.fillRadialGradientColorStops();
    if (colorStops) {
      var ctx = getDummyContext();

      var start = this.fillRadialGradientStartPoint();
      var end = this.fillRadialGradientEndPoint();
      var grd = ctx.createRadialGradient(
        start.x,
        start.y,
        this.fillRadialGradientStartRadius(),
        end.x,
        end.y,
        this.fillRadialGradientEndRadius()
      );

      // build color stops
      for (var n = 0; n < colorStops.length; n += 2) {
        grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string);
      }
      return grd;
    }
  }
  getShadowRGBA() {
    return this._getCache(SHADOW_RGBA, this._getShadowRGBA);
  }
  _getShadowRGBA() {
    if (this.hasShadow()) {
      var rgba = Util.colorToRGBA(this.shadowColor());
      return (
        'rgba(' +
        rgba.r +
        ',' +
        rgba.g +
        ',' +
        rgba.b +
        ',' +
        rgba.a * (this.shadowOpacity() || 1) +
        ')'
      );
    }
  }
  /**
   * returns whether or not the shape will be filled
   * @method
   * @name Konva.Shape#hasFill
   * @returns {Boolean}
   */
  hasFill() {
    return (
      this.fillEnabled() &&
      !!(
        this.fill() ||
        this.fillPatternImage() ||
        this.fillLinearGradientColorStops() ||
        this.fillRadialGradientColorStops()
      )
    );
  }
  /**
   * returns whether or not the shape will be stroked
   * @method
   * @name Konva.Shape#hasStroke
   * @returns {Boolean}
   */
  hasStroke() {
    return (
      this.strokeEnabled() &&
      this.strokeWidth() &&
      !!(this.stroke() || this.strokeLinearGradientColorStops())
      // this.getStrokeRadialGradientColorStops()
    );
  }
  hasHitStroke() {
    const width = this.hitStrokeWidth();

    // on auto just check by stroke
    if (width === 'auto') {
      return this.hasStroke();
    }

    // we should enable hit stroke if stroke is enabled
    // and we have some value from width
    return this.strokeEnabled() && !!width;
  }
  /**
   * determines if point is in the shape, regardless if other shapes are on top of it.  Note: because
   *  this method clears a temporary canvas and then redraws the shape, it performs very poorly if executed many times
   *  consecutively.  Please use the {@link Konva.Stage#getIntersection} method if at all possible
   *  because it performs much better
   * @method
   * @name Konva.Shape#intersects
   * @param {Object} point
   * @param {Number} point.x
   * @param {Number} point.y
   * @returns {Boolean}
   */
  intersects(point) {
    var stage = this.getStage(),
      bufferHitCanvas = stage.bufferHitCanvas,
      p;

    bufferHitCanvas.getContext().clear();
    this.drawHit(bufferHitCanvas);
    p = bufferHitCanvas.context.getImageData(
      Math.round(point.x),
      Math.round(point.y),
      1,
      1
    ).data;
    return p[3] > 0;
  }

  destroy() {
    Node.prototype.destroy.call(this);
    delete shapes[this.colorKey];
    delete this.colorKey;
    return this;
  }
  // why do we need buffer canvas?
  // it give better result when a shape has
  // stroke with fill and with some opacity
  _useBufferCanvas(forceFill?: boolean): boolean {
    // image and sprite still has "fill" as image
    // so they use that method with forced fill
    // it probably will be simpler, then copy/paste the code

    // buffer canvas is available only inside the stage
    if (!this.getStage()) {
      return false;
    }
    // force skip buffer canvas
    if (!this.perfectDrawEnabled()) {
      return false;
    }
    const hasFill = forceFill || this.hasFill();
    const hasStroke = this.hasStroke();
    const isTransparent = this.getAbsoluteOpacity() !== 1;

    if (hasFill && hasStroke && isTransparent) {
      return true;
    }

    const hasShadow = this.hasShadow();
    const strokeForShadow = this.shadowForStrokeEnabled();
    if (hasFill && hasStroke && hasShadow && strokeForShadow) {
      return true;
    }
    return false;
  }
  setStrokeHitEnabled(val: number) {
    Util.warn(
      'strokeHitEnabled property is deprecated. Please use hitStrokeWidth instead.'
    );
    if (val) {
      this.hitStrokeWidth('auto');
    } else {
      this.hitStrokeWidth(0);
    }
  }
  getStrokeHitEnabled() {
    if (this.hitStrokeWidth() === 0) {
      return false;
    } else {
      return true;
    }
  }
  /**
   * return self rectangle (x, y, width, height) of shape.
   * This method are not taken into account transformation and styles.
   * @method
   * @name Konva.Shape#getSelfRect
   * @returns {Object} rect with {x, y, width, height} properties
   * @example
   *
   * rect.getSelfRect();  // return {x:0, y:0, width:rect.width(), height:rect.height()}
   * circle.getSelfRect();  // return {x: - circle.width() / 2, y: - circle.height() / 2, width:circle.width(), height:circle.height()}
   *
   */
  getSelfRect() {
    var size = this.size();
    return {
      x: this._centroid ? -size.width / 2 : 0,
      y: this._centroid ? -size.height / 2 : 0,
      width: size.width,
      height: size.height,
    };
  }
  getClientRect(attrs) {
    attrs = attrs || {};
    var skipTransform = attrs.skipTransform;

    var relativeTo = attrs.relativeTo;

    var fillRect = this.getSelfRect();

    var applyStroke = !attrs.skipStroke && this.hasStroke();
    var strokeWidth = (applyStroke && this.strokeWidth()) || 0;

    var fillAndStrokeWidth = fillRect.width + strokeWidth;
    var fillAndStrokeHeight = fillRect.height + strokeWidth;

    var applyShadow = !attrs.skipShadow && this.hasShadow();
    var shadowOffsetX = applyShadow ? this.shadowOffsetX() : 0;
    var shadowOffsetY = applyShadow ? this.shadowOffsetY() : 0;

    var preWidth = fillAndStrokeWidth + Math.abs(shadowOffsetX);
    var preHeight = fillAndStrokeHeight + Math.abs(shadowOffsetY);

    var blurRadius = (applyShadow && this.shadowBlur()) || 0;

    var width = preWidth + blurRadius * 2;
    var height = preHeight + blurRadius * 2;

    // if stroke, for example = 3
    // we need to set x to 1.5, but after Math.round it will be 2
    // as we have additional offset we need to increase width and height by 1 pixel
    var roundingOffset = 0;
    if (Math.round(strokeWidth / 2) !== strokeWidth / 2) {
      roundingOffset = 1;
    }
    var rect = {
      width: width + roundingOffset,
      height: height + roundingOffset,
      x:
        -Math.round(strokeWidth / 2 + blurRadius) +
        Math.min(shadowOffsetX, 0) +
        fillRect.x,
      y:
        -Math.round(strokeWidth / 2 + blurRadius) +
        Math.min(shadowOffsetY, 0) +
        fillRect.y,
    };
    if (!skipTransform) {
      return this._transformedRect(rect, relativeTo);
    }
    return rect;
  }
  drawScene(can?: SceneCanvas, top?: Node) {
    // basically there are 3 drawing modes
    // 1 - simple drawing when nothing is cached.
    // 2 - when we are caching current
    // 3 - when node is cached and we need to draw it into layer

    var layer = this.getLayer(),
      canvas = can || layer.getCanvas(),
      context = canvas.getContext() as SceneContext,
      cachedCanvas = this._getCanvasCache(),
      drawFunc = this.sceneFunc(),
      hasShadow = this.hasShadow(),
      hasStroke = this.hasStroke(),
      stage,
      bufferCanvas,
      bufferContext;

    var caching = canvas.isCache;
    var skipBuffer = canvas.isCache;
    var cachingSelf = top === this;

    if (!this.isVisible() && !caching) {
      return this;
    }
    // if node is cached we just need to draw from cache
    if (cachedCanvas) {
      context.save();

      var m = this.getAbsoluteTransform(top).getMatrix();
      context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
      this._drawCachedSceneCanvas(context);
      context.restore();
      return this;
    }

    if (!drawFunc) {
      return this;
    }

    context.save();
    // if buffer canvas is needed
    if (this._useBufferCanvas() && !skipBuffer) {
      stage = this.getStage();
      bufferCanvas = stage.bufferCanvas;
      bufferContext = bufferCanvas.getContext();
      bufferContext.clear();
      bufferContext.save();
      bufferContext._applyLineJoin(this);
      // layer might be undefined if we are using cache before adding to layer
      var o = this.getAbsoluteTransform(top).getMatrix();
      bufferContext.transform(o[0], o[1], o[2], o[3], o[4], o[5]);

      drawFunc.call(this, bufferContext, this);
      bufferContext.restore();

      var ratio = bufferCanvas.pixelRatio;

      if (hasShadow) {
        context._applyShadow(this);
      }
      context._applyOpacity(this);
      context._applyGlobalCompositeOperation(this);
      context.drawImage(
        bufferCanvas._canvas,
        0,
        0,
        bufferCanvas.width / ratio,
        bufferCanvas.height / ratio
      );
    } else {
      context._applyLineJoin(this);

      if (!cachingSelf) {
        var o = this.getAbsoluteTransform(top).getMatrix();
        context.transform(o[0], o[1], o[2], o[3], o[4], o[5]);
        context._applyOpacity(this);
        context._applyGlobalCompositeOperation(this);
      }

      if (hasShadow) {
        context._applyShadow(this);
      }

      drawFunc.call(this, context, this);
    }
    context.restore();
    return this;
  }
  drawHit(can?: HitCanvas, top?: Node) {
    if (!this.shouldDrawHit(top)) {
      return this;
    }

    var layer = this.getLayer(),
      canvas = can || layer.hitCanvas,
      context = canvas && canvas.getContext(),
      drawFunc = this.hitFunc() || this.sceneFunc(),
      cachedCanvas = this._getCanvasCache(),
      cachedHitCanvas = cachedCanvas && cachedCanvas.hit;

    if (!this.colorKey) {
      console.log(this);
      Util.warn(
        'Looks like your canvas has a destroyed shape in it. Do not reuse shape after you destroyed it. See the shape in logs above. If you want to reuse shape you should call remove() instead of destroy()'
      );
    }

    if (cachedHitCanvas) {
      context.save();

      var m = this.getAbsoluteTransform(top).getMatrix();
      context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);

      this._drawCachedHitCanvas(context);
      context.restore();
      return this;
    }
    if (!drawFunc) {
      return this;
    }
    context.save();
    context._applyLineJoin(this);

    const selfCache = this === top;
    if (!selfCache) {
      var o = this.getAbsoluteTransform(top).getMatrix();
      context.transform(o[0], o[1], o[2], o[3], o[4], o[5]);
    }
    drawFunc.call(this, context, this);
    context.restore();
    return this;
  }
  /**
   * draw hit graph using the cached scene canvas
   * @method
   * @name Konva.Shape#drawHitFromCache
   * @param {Integer} alphaThreshold alpha channel threshold that determines whether or not
   *  a pixel should be drawn onto the hit graph.  Must be a value between 0 and 255.
   *  The default is 0
   * @returns {Konva.Shape}
   * @example
   * shape.cache();
   * shape.drawHitFromCache();
   */
  drawHitFromCache(alphaThreshold = 0) {
    var cachedCanvas = this._getCanvasCache(),
      sceneCanvas = this._getCachedSceneCanvas(),
      hitCanvas = cachedCanvas.hit,
      hitContext = hitCanvas.getContext(),
      hitWidth = hitCanvas.getWidth(),
      hitHeight = hitCanvas.getHeight(),
      hitImageData,
      hitData,
      len,
      rgbColorKey,
      i,
      alpha;

    hitContext.clear();
    hitContext.drawImage(sceneCanvas._canvas, 0, 0, hitWidth, hitHeight);

    try {
      hitImageData = hitContext.getImageData(0, 0, hitWidth, hitHeight);
      hitData = hitImageData.data;
      len = hitData.length;
      rgbColorKey = Util._hexToRgb(this.colorKey);

      // replace non transparent pixels with color key
      for (i = 0; i < len; i += 4) {
        alpha = hitData[i + 3];
        if (alpha > alphaThreshold) {
          hitData[i] = rgbColorKey.r;
          hitData[i + 1] = rgbColorKey.g;
          hitData[i + 2] = rgbColorKey.b;
          hitData[i + 3] = 255;
        } else {
          hitData[i + 3] = 0;
        }
      }
      hitContext.putImageData(hitImageData, 0, 0);
    } catch (e) {
      Util.error(
        'Unable to draw hit graph from cached scene canvas. ' + e.message
      );
    }

    return this;
  }

  hasPointerCapture(pointerId: number): boolean {
    return PointerEvents.hasPointerCapture(pointerId, this);
  }

  setPointerCapture(pointerId: number) {
    PointerEvents.setPointerCapture(pointerId, this);
  }

  releaseCapture(pointerId: number) {
    PointerEvents.releaseCapture(pointerId, this);
  }

  draggable: GetSet<boolean, this>;
  embossBlend: GetSet<boolean, this>;

  dash: GetSet<number[], this>;
  dashEnabled: GetSet<boolean, this>;
  dashOffset: GetSet<number, this>;
  fill: GetSet<string, this>;
  fillEnabled: GetSet<boolean, this>;
  fillLinearGradientColorStops: GetSet<Array<number | string>, this>;
  fillLinearGradientStartPoint: GetSet<Vector2d, this>;
  fillLinearGradientStartPointX: GetSet<number, this>;
  fillLinearGradientStartPointY: GetSet<number, this>;
  fillLinearGradientEndPoint: GetSet<Vector2d, this>;
  fillLinearGradientEndPointX: GetSet<number, this>;
  fillLinearGradientEndPointY: GetSet<number, this>;
  fillLinearRadialStartPoint: GetSet<Vector2d, this>;
  fillLinearRadialStartPointX: GetSet<number, this>;
  fillLinearRadialStartPointY: GetSet<number, this>;
  fillLinearRadialEndPoint: GetSet<Vector2d, this>;
  fillLinearRadialEndPointX: GetSet<number, this>;
  fillLinearRadialEndPointY: GetSet<number, this>;
  fillPatternImage: GetSet<HTMLImageElement, this>;
  fillRadialGradientStartRadius: GetSet<number, this>;
  fillRadialGradientEndRadius: GetSet<number, this>;
  fillRadialGradientColorStops: GetSet<Array<number | string>, this>;
  fillRadialGradientStartPoint: GetSet<Vector2d, this>;
  fillRadialGradientStartPointX: GetSet<number, this>;
  fillRadialGradientStartPointY: GetSet<number, this>;
  fillRadialGradientEndPoint: GetSet<Vector2d, this>;
  fillRadialGradientEndPointX: GetSet<number, this>;
  fillRadialGradientEndPointY: GetSet<number, this>;
  fillPatternOffset: GetSet<Vector2d, this>;
  fillPatternOffsetX: GetSet<number, this>;
  fillPatternOffsetY: GetSet<number, this>;
  fillPatternRepeat: GetSet<string, this>;
  fillPatternRotation: GetSet<number, this>;
  fillPatternScale: GetSet<Vector2d, this>;
  fillPatternScaleX: GetSet<number, this>;
  fillPatternScaleY: GetSet<number, this>;
  fillPatternX: GetSet<number, this>;
  fillPatternY: GetSet<number, this>;
  fillPriority: GetSet<string, this>;
  hitFunc: GetSet<ShapeConfigHandler<this>, this>;
  lineCap: GetSet<string, this>;
  lineJoin: GetSet<string, this>;
  perfectDrawEnabled: GetSet<boolean, this>;
  sceneFunc: GetSet<ShapeConfigHandler<this>, this>;
  shadowColor: GetSet<string, this>;
  shadowEnabled: GetSet<boolean, this>;
  shadowForStrokeEnabled: GetSet<boolean, this>;
  shadowOffset: GetSet<Vector2d, this>;
  shadowOffsetX: GetSet<number, this>;
  shadowOffsetY: GetSet<number, this>;
  shadowOpacity: GetSet<number, this>;
  shadowBlur: GetSet<number, this>;
  stroke: GetSet<string, this>;
  strokeEnabled: GetSet<boolean, this>;
  strokeScaleEnabled: GetSet<boolean, this>;
  strokeHitEnabled: GetSet<boolean, this>;
  strokeWidth: GetSet<number, this>;
  hitStrokeWidth: GetSet<number | 'auto', this>;
  strokeLinearGradientColorStops: GetSet<Array<number | string>, this>;
}

Shape.prototype._fillFunc = _fillFunc;
Shape.prototype._strokeFunc = _strokeFunc;
Shape.prototype._fillFuncHit = _fillFuncHit;
Shape.prototype._strokeFuncHit = _strokeFuncHit;

Shape.prototype._centroid = false;
Shape.prototype.nodeType = 'Shape';
_registerNode(Shape);

// add getters and setters
Factory.addGetterSetter(Shape, 'stroke', undefined, getStringValidator());

/**
 * get/set stroke color
 * @name Konva.Shape#stroke
 * @method
 * @param {String} color
 * @returns {String}
 * @example
 * // get stroke color
 * var stroke = shape.stroke();
 *
 * // set stroke color with color string
 * shape.stroke('green');
 *
 * // set stroke color with hex
 * shape.stroke('#00ff00');
 *
 * // set stroke color with rgb
 * shape.stroke('rgb(0,255,0)');
 *
 * // set stroke color with rgba and make it 50% opaque
 * shape.stroke('rgba(0,255,0,0.5');
 */

Factory.addGetterSetter(Shape, 'strokeWidth', 2, getNumberValidator());

/**
 * get/set stroke width
 * @name Konva.Shape#strokeWidth
 * @method
 * @param {Number} strokeWidth
 * @returns {Number}
 * @example
 * // get stroke width
 * var strokeWidth = shape.strokeWidth();
 *
 * // set stroke width
 * shape.strokeWidth(10);
 */

Factory.addGetterSetter(
  Shape,
  'hitStrokeWidth',
  'auto',
  getNumberOrAutoValidator()
);

/**
 * get/set stroke width for hit detection. Default value is "auto", it means it will be equals to strokeWidth
 * @name Konva.Shape#hitStrokeWidth
 * @method
 * @param {Number} hitStrokeWidth
 * @returns {Number}
 * @example
 * // get stroke width
 * var hitStrokeWidth = shape.hitStrokeWidth();
 *
 * // set hit stroke width
 * shape.hitStrokeWidth(20);
 * // set hit stroke width always equals to scene stroke width
 * shape.hitStrokeWidth('auto');
 */

Factory.addGetterSetter(Shape, 'strokeHitEnabled', true, getBooleanValidator());

/**
 * **deprecated, use hitStrokeWidth instead!** get/set strokeHitEnabled property. Useful for performance optimization.
 * You may set `shape.strokeHitEnabled(false)`. In this case stroke will be no draw on hit canvas, so hit area
 * of shape will be decreased (by lineWidth / 2). Remember that non closed line with `strokeHitEnabled = false`
 * will be not drawn on hit canvas, that is mean line will no trigger pointer events (like mouseover)
 * Default value is true.
 * @name Konva.Shape#strokeHitEnabled
 * @method
 * @param {Boolean} strokeHitEnabled
 * @returns {Boolean}
 * @example
 * // get strokeHitEnabled
 * var strokeHitEnabled = shape.strokeHitEnabled();
 *
 * // set strokeHitEnabled
 * shape.strokeHitEnabled();
 */

Factory.addGetterSetter(
  Shape,
  'perfectDrawEnabled',
  true,
  getBooleanValidator()
);

/**
 * get/set perfectDrawEnabled. If a shape has fill, stroke and opacity you may set `perfectDrawEnabled` to false to improve performance.
 * See http://konvajs.org/docs/performance/Disable_Perfect_Draw.html for more information.
 * Default value is true
 * @name Konva.Shape#perfectDrawEnabled
 * @method
 * @param {Boolean} perfectDrawEnabled
 * @returns {Boolean}
 * @example
 * // get perfectDrawEnabled
 * var perfectDrawEnabled = shape.perfectDrawEnabled();
 *
 * // set perfectDrawEnabled
 * shape.perfectDrawEnabled();
 */

Factory.addGetterSetter(
  Shape,
  'shadowForStrokeEnabled',
  true,
  getBooleanValidator()
);

/**
 * get/set shadowForStrokeEnabled. Useful for performance optimization.
 * You may set `shape.shadowForStrokeEnabled(false)`. In this case stroke will no effect shadow.
 * Remember if you set `shadowForStrokeEnabled = false` for non closed line - that line will have no shadow!.
 * Default value is true
 * @name Konva.Shape#shadowForStrokeEnabled
 * @method
 * @param {Boolean} shadowForStrokeEnabled
 * @returns {Boolean}
 * @example
 * // get shadowForStrokeEnabled
 * var shadowForStrokeEnabled = shape.shadowForStrokeEnabled();
 *
 * // set shadowForStrokeEnabled
 * shape.shadowForStrokeEnabled();
 */

Factory.addGetterSetter(Shape, 'lineJoin');

/**
 * get/set line join.  Can be miter, round, or bevel.  The
 *  default is miter
 * @name Konva.Shape#lineJoin
 * @method
 * @param {String} lineJoin
 * @returns {String}
 * @example
 * // get line join
 * var lineJoin = shape.lineJoin();
 *
 * // set line join
 * shape.lineJoin('round');
 */

Factory.addGetterSetter(Shape, 'lineCap');

/**
 * get/set line cap.  Can be butt, round, or square
 * @name Konva.Shape#lineCap
 * @method
 * @param {String} lineCap
 * @returns {String}
 * @example
 * // get line cap
 * var lineCap = shape.lineCap();
 *
 * // set line cap
 * shape.lineCap('round');
 */

Factory.addGetterSetter(Shape, 'sceneFunc');

/**
 * get/set scene draw function. That function is used to draw the shape on a canvas.
 * Also that function will be used to draw hit area of the shape, in case if hitFunc is not defined.
 * @name Konva.Shape#sceneFunc
 * @method
 * @param {Function} drawFunc drawing function
 * @returns {Function}
 * @example
 * // get scene draw function
 * var sceneFunc = shape.sceneFunc();
 *
 * // set scene draw function
 * shape.sceneFunc(function(context, shape) {
 *   context.beginPath();
 *   context.rect(0, 0, shape.width(), shape.height());
 *   context.closePath();
 *   // important Konva method that fill and stroke shape from its properties
 *   // like stroke and fill
 *   context.fillStrokeShape(shape);
 * });
 */

Factory.addGetterSetter(Shape, 'hitFunc');

/**
 * get/set hit draw function. That function is used to draw custom hit area of a shape.
 * @name Konva.Shape#hitFunc
 * @method
 * @param {Function} drawFunc drawing function
 * @returns {Function}
 * @example
 * // get hit draw function
 * var hitFunc = shape.hitFunc();
 *
 * // set hit draw function
 * shape.hitFunc(function(context) {
 *   context.beginPath();
 *   context.rect(0, 0, shape.width(), shape.height());
 *   context.closePath();
 *   // important Konva method that fill and stroke shape from its properties
 *   context.fillStrokeShape(shape);
 * });
 */

Factory.addGetterSetter(Shape, 'dash');

/**
 * get/set dash array for stroke.
 * @name Konva.Shape#dash
 * @method
 * @param {Array} dash
 * @returns {Array}
 * @example
 *  // apply dashed stroke that is 10px long and 5 pixels apart
 *  line.dash([10, 5]);
 *  // apply dashed stroke that is made up of alternating dashed
 *  // lines that are 10px long and 20px apart, and dots that have
 *  // a radius of 5px and are 20px apart
 *  line.dash([10, 20, 0.001, 20]);
 */

Factory.addGetterSetter(Shape, 'dashOffset', 0, getNumberValidator());

/**
 * get/set dash offset for stroke.
 * @name Konva.Shape#dash
 * @method
 * @param {Number} dash offset
 * @returns {Number}
 * @example
 *  // apply dashed stroke that is 10px long and 5 pixels apart with an offset of 5px
 *  line.dash([10, 5]);
 *  line.dashOffset(5);
 */

Factory.addGetterSetter(Shape, 'shadowColor', undefined, getStringValidator());

/**
 * get/set shadow color
 * @name Konva.Shape#shadowColor
 * @method
 * @param {String} color
 * @returns {String}
 * @example
 * // get shadow color
 * var shadow = shape.shadowColor();
 *
 * // set shadow color with color string
 * shape.shadowColor('green');
 *
 * // set shadow color with hex
 * shape.shadowColor('#00ff00');
 *
 * // set shadow color with rgb
 * shape.shadowColor('rgb(0,255,0)');
 *
 * // set shadow color with rgba and make it 50% opaque
 * shape.shadowColor('rgba(0,255,0,0.5');
 */

Factory.addGetterSetter(Shape, 'shadowBlur', 0, getNumberValidator());

/**
 * get/set shadow blur
 * @name Konva.Shape#shadowBlur
 * @method
 * @param {Number} blur
 * @returns {Number}
 * @example
 * // get shadow blur
 * var shadowBlur = shape.shadowBlur();
 *
 * // set shadow blur
 * shape.shadowBlur(10);
 */

Factory.addGetterSetter(Shape, 'shadowOpacity', 1, getNumberValidator());

/**
 * get/set shadow opacity.  must be a value between 0 and 1
 * @name Konva.Shape#shadowOpacity
 * @method
 * @param {Number} opacity
 * @returns {Number}
 * @example
 * // get shadow opacity
 * var shadowOpacity = shape.shadowOpacity();
 *
 * // set shadow opacity
 * shape.shadowOpacity(0.5);
 */

Factory.addComponentsGetterSetter(Shape, 'shadowOffset', ['x', 'y']);

/**
 * get/set shadow offset
 * @name Konva.Shape#shadowOffset
 * @method
 * @param {Object} offset
 * @param {Number} offset.x
 * @param {Number} offset.y
 * @returns {Object}
 * @example
 * // get shadow offset
 * var shadowOffset = shape.shadowOffset();
 *
 * // set shadow offset
 * shape.shadowOffset({
 *   x: 20,
 *   y: 10
 * });
 */

Factory.addGetterSetter(Shape, 'shadowOffsetX', 0, getNumberValidator());

/**
 * get/set shadow offset x
 * @name Konva.Shape#shadowOffsetX
 * @method
 * @param {Number} x
 * @returns {Number}
 * @example
 * // get shadow offset x
 * var shadowOffsetX = shape.shadowOffsetX();
 *
 * // set shadow offset x
 * shape.shadowOffsetX(5);
 */

Factory.addGetterSetter(Shape, 'shadowOffsetY', 0, getNumberValidator());

/**
 * get/set shadow offset y
 * @name Konva.Shape#shadowOffsetY
 * @method
 * @param {Number} y
 * @returns {Number}
 * @example
 * // get shadow offset y
 * var shadowOffsetY = shape.shadowOffsetY();
 *
 * // set shadow offset y
 * shape.shadowOffsetY(5);
 */

Factory.addGetterSetter(Shape, 'fillPatternImage');

/**
 * get/set fill pattern image
 * @name Konva.Shape#fillPatternImage
 * @method
 * @param {Image} image object
 * @returns {Image}
 * @example
 * // get fill pattern image
 * var fillPatternImage = shape.fillPatternImage();
 *
 * // set fill pattern image
 * var imageObj = new Image();
 * imageObj.onload = function() {
 *   shape.fillPatternImage(imageObj);
 * };
 * imageObj.src = 'path/to/image/jpg';
 */

Factory.addGetterSetter(Shape, 'fill', undefined, getStringValidator());

/**
 * get/set fill color
 * @name Konva.Shape#fill
 * @method
 * @param {String} color
 * @returns {String}
 * @example
 * // get fill color
 * var fill = shape.fill();
 *
 * // set fill color with color string
 * shape.fill('green');
 *
 * // set fill color with hex
 * shape.fill('#00ff00');
 *
 * // set fill color with rgb
 * shape.fill('rgb(0,255,0)');
 *
 * // set fill color with rgba and make it 50% opaque
 * shape.fill('rgba(0,255,0,0.5');
 *
 * // shape without fill
 * shape.fill(null);
 */

Factory.addGetterSetter(Shape, 'fillPatternX', 0, getNumberValidator());

/**
 * get/set fill pattern x
 * @name Konva.Shape#fillPatternX
 * @method
 * @param {Number} x
 * @returns {Number}
 * @example
 * // get fill pattern x
 * var fillPatternX = shape.fillPatternX();
 * // set fill pattern x
 * shape.fillPatternX(20);
 */

Factory.addGetterSetter(Shape, 'fillPatternY', 0, getNumberValidator());

/**
 * get/set fill pattern y
 * @name Konva.Shape#fillPatternY
 * @method
 * @param {Number} y
 * @returns {Number}
 * @example
 * // get fill pattern y
 * var fillPatternY = shape.fillPatternY();
 * // set fill pattern y
 * shape.fillPatternY(20);
 */

Factory.addGetterSetter(Shape, 'fillLinearGradientColorStops');

/**
 * get/set fill linear gradient color stops
 * @name Konva.Shape#fillLinearGradientColorStops
 * @method
 * @param {Array} colorStops
 * @returns {Array} colorStops
 * @example
 * // get fill linear gradient color stops
 * var colorStops = shape.fillLinearGradientColorStops();
 *
 * // create a linear gradient that starts with red, changes to blue
 * // halfway through, and then changes to green
 * shape.fillLinearGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green');
 */

Factory.addGetterSetter(Shape, 'strokeLinearGradientColorStops');

/**
 * get/set stroke linear gradient color stops
 * @name Konva.Shape#strokeLinearGradientColorStops
 * @method
 * @param {Array} colorStops
 * @returns {Array} colorStops
 * @example
 * // get stroke linear gradient color stops
 * var colorStops = shape.strokeLinearGradientColorStops();
 *
 * // create a linear gradient that starts with red, changes to blue
 * // halfway through, and then changes to green
 * shape.strokeLinearGradientColorStops([0, 'red', 0.5, 'blue', 1, 'green']);
 */

Factory.addGetterSetter(Shape, 'fillRadialGradientStartRadius', 0);

/**
 * get/set fill radial gradient start radius
 * @name Konva.Shape#fillRadialGradientStartRadius
 * @method
 * @param {Number} radius
 * @returns {Number}
 * @example
 * // get radial gradient start radius
 * var startRadius = shape.fillRadialGradientStartRadius();
 *
 * // set radial gradient start radius
 * shape.fillRadialGradientStartRadius(0);
 */

Factory.addGetterSetter(Shape, 'fillRadialGradientEndRadius', 0);

/**
 * get/set fill radial gradient end radius
 * @name Konva.Shape#fillRadialGradientEndRadius
 * @method
 * @param {Number} radius
 * @returns {Number}
 * @example
 * // get radial gradient end radius
 * var endRadius = shape.fillRadialGradientEndRadius();
 *
 * // set radial gradient end radius
 * shape.fillRadialGradientEndRadius(100);
 */

Factory.addGetterSetter(Shape, 'fillRadialGradientColorStops');

/**
 * get/set fill radial gradient color stops
 * @name Konva.Shape#fillRadialGradientColorStops
 * @method
 * @param {Number} colorStops
 * @returns {Array}
 * @example
 * // get fill radial gradient color stops
 * var colorStops = shape.fillRadialGradientColorStops();
 *
 * // create a radial gradient that starts with red, changes to blue
 * // halfway through, and then changes to green
 * shape.fillRadialGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green');
 */

Factory.addGetterSetter(Shape, 'fillPatternRepeat', 'repeat');

/**
 * get/set fill pattern repeat.  Can be 'repeat', 'repeat-x', 'repeat-y', or 'no-repeat'.  The default is 'repeat'
 * @name Konva.Shape#fillPatternRepeat
 * @method
 * @param {String} repeat
 * @returns {String}
 * @example
 * // get fill pattern repeat
 * var repeat = shape.fillPatternRepeat();
 *
 * // repeat pattern in x direction only
 * shape.fillPatternRepeat('repeat-x');
 *
 * // do not repeat the pattern
 * shape.fillPatternRepeat('no-repeat');
 */

Factory.addGetterSetter(Shape, 'fillEnabled', true);

/**
 * get/set fill enabled flag
 * @name Konva.Shape#fillEnabled
 * @method
 * @param {Boolean} enabled
 * @returns {Boolean}
 * @example
 * // get fill enabled flag
 * var fillEnabled = shape.fillEnabled();
 *
 * // disable fill
 * shape.fillEnabled(false);
 *
 * // enable fill
 * shape.fillEnabled(true);
 */

Factory.addGetterSetter(Shape, 'strokeEnabled', true);

/**
 * get/set stroke enabled flag
 * @name Konva.Shape#strokeEnabled
 * @method
 * @param {Boolean} enabled
 * @returns {Boolean}
 * @example
 * // get stroke enabled flag
 * var strokeEnabled = shape.strokeEnabled();
 *
 * // disable stroke
 * shape.strokeEnabled(false);
 *
 * // enable stroke
 * shape.strokeEnabled(true);
 */

Factory.addGetterSetter(Shape, 'shadowEnabled', true);

/**
 * get/set shadow enabled flag
 * @name Konva.Shape#shadowEnabled
 * @method
 * @param {Boolean} enabled
 * @returns {Boolean}
 * @example
 * // get shadow enabled flag
 * var shadowEnabled = shape.shadowEnabled();
 *
 * // disable shadow
 * shape.shadowEnabled(false);
 *
 * // enable shadow
 * shape.shadowEnabled(true);
 */

Factory.addGetterSetter(Shape, 'dashEnabled', true);

/**
 * get/set dash enabled flag
 * @name Konva.Shape#dashEnabled
 * @method
 * @param {Boolean} enabled
 * @returns {Boolean}
 * @example
 * // get dash enabled flag
 * var dashEnabled = shape.dashEnabled();
 *
 * // disable dash
 * shape.dashEnabled(false);
 *
 * // enable dash
 * shape.dashEnabled(true);
 */

Factory.addGetterSetter(Shape, 'strokeScaleEnabled', true);

/**
 * get/set strokeScale enabled flag
 * @name Konva.Shape#strokeScaleEnabled
 * @method
 * @param {Boolean} enabled
 * @returns {Boolean}
 * @example
 * // get stroke scale enabled flag
 * var strokeScaleEnabled = shape.strokeScaleEnabled();
 *
 * // disable stroke scale
 * shape.strokeScaleEnabled(false);
 *
 * // enable stroke scale
 * shape.strokeScaleEnabled(true);
 */

Factory.addGetterSetter(Shape, 'fillPriority', 'color');

/**
 * get/set fill priority.  can be color, pattern, linear-gradient, or radial-gradient.  The default is color.
 *   This is handy if you want to toggle between different fill types.
 * @name Konva.Shape#fillPriority
 * @method
 * @param {String} priority
 * @returns {String}
 * @example
 * // get fill priority
 * var fillPriority = shape.fillPriority();
 *
 * // set fill priority
 * shape.fillPriority('linear-gradient');
 */

Factory.addComponentsGetterSetter(Shape, 'fillPatternOffset', ['x', 'y']);

/**
 * get/set fill pattern offset
 * @name Konva.Shape#fillPatternOffset
 * @method
 * @param {Object} offset
 * @param {Number} offset.x
 * @param {Number} offset.y
 * @returns {Object}
 * @example
 * // get fill pattern offset
 * var patternOffset = shape.fillPatternOffset();
 *
 * // set fill pattern offset
 * shape.fillPatternOffset({
 *   x: 20,
 *   y: 10
 * });
 */

Factory.addGetterSetter(Shape, 'fillPatternOffsetX', 0, getNumberValidator());

/**
 * get/set fill pattern offset x
 * @name Konva.Shape#fillPatternOffsetX
 * @method
 * @param {Number} x
 * @returns {Number}
 * @example
 * // get fill pattern offset x
 * var patternOffsetX = shape.fillPatternOffsetX();
 *
 * // set fill pattern offset x
 * shape.fillPatternOffsetX(20);
 */

Factory.addGetterSetter(Shape, 'fillPatternOffsetY', 0, getNumberValidator());

/**
 * get/set fill pattern offset y
 * @name Konva.Shape#fillPatternOffsetY
 * @method
 * @param {Number} y
 * @returns {Number}
 * @example
 * // get fill pattern offset y
 * var patternOffsetY = shape.fillPatternOffsetY();
 *
 * // set fill pattern offset y
 * shape.fillPatternOffsetY(10);
 */

Factory.addComponentsGetterSetter(Shape, 'fillPatternScale', ['x', 'y']);

/**
 * get/set fill pattern scale
 * @name Konva.Shape#fillPatternScale
 * @method
 * @param {Object} scale
 * @param {Number} scale.x
 * @param {Number} scale.y
 * @returns {Object}
 * @example
 * // get fill pattern scale
 * var patternScale = shape.fillPatternScale();
 *
 * // set fill pattern scale
 * shape.fillPatternScale({
 *   x: 2,
 *   y: 2
 * });
 */

Factory.addGetterSetter(Shape, 'fillPatternScaleX', 1, getNumberValidator());

/**
 * get/set fill pattern scale x
 * @name Konva.Shape#fillPatternScaleX
 * @method
 * @param {Number} x
 * @returns {Number}
 * @example
 * // get fill pattern scale x
 * var patternScaleX = shape.fillPatternScaleX();
 *
 * // set fill pattern scale x
 * shape.fillPatternScaleX(2);
 */

Factory.addGetterSetter(Shape, 'fillPatternScaleY', 1, getNumberValidator());

/**
 * get/set fill pattern scale y
 * @name Konva.Shape#fillPatternScaleY
 * @method
 * @param {Number} y
 * @returns {Number}
 * @example
 * // get fill pattern scale y
 * var patternScaleY = shape.fillPatternScaleY();
 *
 * // set fill pattern scale y
 * shape.fillPatternScaleY(2);
 */

Factory.addComponentsGetterSetter(Shape, 'fillLinearGradientStartPoint', [
  'x',
  'y',
]);

/**
 * get/set fill linear gradient start point
 * @name Konva.Shape#fillLinearGradientStartPoint
 * @method
 * @param {Object} startPoint
 * @param {Number} startPoint.x
 * @param {Number} startPoint.y
 * @returns {Object}
 * @example
 * // get fill linear gradient start point
 * var startPoint = shape.fillLinearGradientStartPoint();
 *
 * // set fill linear gradient start point
 * shape.fillLinearGradientStartPoint({
 *   x: 20,
 *   y: 10
 * });
 */

Factory.addComponentsGetterSetter(Shape, 'strokeLinearGradientStartPoint', [
  'x',
  'y',
]);

/**
 * get/set stroke linear gradient start point
 * @name Konva.Shape#strokeLinearGradientStartPoint
 * @method
 * @param {Object} startPoint
 * @param {Number} startPoint.x
 * @param {Number} startPoint.y
 * @returns {Object}
 * @example
 * // get stroke linear gradient start point
 * var startPoint = shape.strokeLinearGradientStartPoint();
 *
 * // set stroke linear gradient start point
 * shape.strokeLinearGradientStartPoint({
 *   x: 20,
 *   y: 10
 * });
 */

Factory.addGetterSetter(Shape, 'fillLinearGradientStartPointX', 0);

/**
 * get/set fill linear gradient start point x
 * @name Konva.Shape#fillLinearGradientStartPointX
 * @method
 * @param {Number} x
 * @returns {Number}
 * @example
 * // get fill linear gradient start point x
 * var startPointX = shape.fillLinearGradientStartPointX();
 *
 * // set fill linear gradient start point x
 * shape.fillLinearGradientStartPointX(20);
 */

Factory.addGetterSetter(Shape, 'strokeLinearGradientStartPointX', 0);

/**
 * get/set stroke linear gradient start point x
 * @name Konva.Shape#linearLinearGradientStartPointX
 * @method
 * @param {Number} x
 * @returns {Number}
 * @example
 * // get stroke linear gradient start point x
 * var startPointX = shape.strokeLinearGradientStartPointX();
 *
 * // set stroke linear gradient start point x
 * shape.strokeLinearGradientStartPointX(20);
 */

Factory.addGetterSetter(Shape, 'fillLinearGradientStartPointY', 0);

/**
 * get/set fill linear gradient start point y
 * @name Konva.Shape#fillLinearGradientStartPointY
 * @method
 * @param {Number} y
 * @returns {Number}
 * @example
 * // get fill linear gradient start point y
 * var startPointY = shape.fillLinearGradientStartPointY();
 *
 * // set fill linear gradient start point y
 * shape.fillLinearGradientStartPointY(20);
 */

Factory.addGetterSetter(Shape, 'strokeLinearGradientStartPointY', 0);
/**
 * get/set stroke linear gradient start point y
 * @name Konva.Shape#strokeLinearGradientStartPointY
 * @method
 * @param {Number} y
 * @returns {Number}
 * @example
 * // get stroke linear gradient start point y
 * var startPointY = shape.strokeLinearGradientStartPointY();
 *
 * // set stroke linear gradient start point y
 * shape.strokeLinearGradientStartPointY(20);
 */

Factory.addComponentsGetterSetter(Shape, 'fillLinearGradientEndPoint', [
  'x',
  'y',
]);

/**
 * get/set fill linear gradient end point
 * @name Konva.Shape#fillLinearGradientEndPoint
 * @method
 * @param {Object} endPoint
 * @param {Number} endPoint.x
 * @param {Number} endPoint.y
 * @returns {Object}
 * @example
 * // get fill linear gradient end point
 * var endPoint = shape.fillLinearGradientEndPoint();
 *
 * // set fill linear gradient end point
 * shape.fillLinearGradientEndPoint({
 *   x: 20,
 *   y: 10
 * });
 */

Factory.addComponentsGetterSetter(Shape, 'strokeLinearGradientEndPoint', [
  'x',
  'y',
]);

/**
 * get/set stroke linear gradient end point
 * @name Konva.Shape#strokeLinearGradientEndPoint
 * @method
 * @param {Object} endPoint
 * @param {Number} endPoint.x
 * @param {Number} endPoint.y
 * @returns {Object}
 * @example
 * // get stroke linear gradient end point
 * var endPoint = shape.strokeLinearGradientEndPoint();
 *
 * // set stroke linear gradient end point
 * shape.strokeLinearGradientEndPoint({
 *   x: 20,
 *   y: 10
 * });
 */

Factory.addGetterSetter(Shape, 'fillLinearGradientEndPointX', 0);
/**
 * get/set fill linear gradient end point x
 * @name Konva.Shape#fillLinearGradientEndPointX
 * @method
 * @param {Number} x
 * @returns {Number}
 * @example
 * // get fill linear gradient end point x
 * var endPointX = shape.fillLinearGradientEndPointX();
 *
 * // set fill linear gradient end point x
 * shape.fillLinearGradientEndPointX(20);
 */

Factory.addGetterSetter(Shape, 'strokeLinearGradientEndPointX', 0);
/**
 * get/set fill linear gradient end point x
 * @name Konva.Shape#strokeLinearGradientEndPointX
 * @method
 * @param {Number} x
 * @returns {Number}
 * @example
 * // get stroke linear gradient end point x
 * var endPointX = shape.strokeLinearGradientEndPointX();
 *
 * // set stroke linear gradient end point x
 * shape.strokeLinearGradientEndPointX(20);
 */

Factory.addGetterSetter(Shape, 'fillLinearGradientEndPointY', 0);
/**
 * get/set fill linear gradient end point y
 * @name Konva.Shape#fillLinearGradientEndPointY
 * @method
 * @param {Number} y
 * @returns {Number}
 * @example
 * // get fill linear gradient end point y
 * var endPointY = shape.fillLinearGradientEndPointY();
 *
 * // set fill linear gradient end point y
 * shape.fillLinearGradientEndPointY(20);
 */

Factory.addGetterSetter(Shape, 'strokeLinearGradientEndPointY', 0);
/**
 * get/set stroke linear gradient end point y
 * @name Konva.Shape#strokeLinearGradientEndPointY
 * @method
 * @param {Number} y
 * @returns {Number}
 * @example
 * // get stroke linear gradient end point y
 * var endPointY = shape.strokeLinearGradientEndPointY();
 *
 * // set stroke linear gradient end point y
 * shape.strokeLinearGradientEndPointY(20);
 */

Factory.addComponentsGetterSetter(Shape, 'fillRadialGradientStartPoint', [
  'x',
  'y',
]);

/**
 * get/set fill radial gradient start point
 * @name Konva.Shape#fillRadialGradientStartPoint
 * @method
 * @param {Object} startPoint
 * @param {Number} startPoint.x
 * @param {Number} startPoint.y
 * @returns {Object}
 * @example
 * // get fill radial gradient start point
 * var startPoint = shape.fillRadialGradientStartPoint();
 *
 * // set fill radial gradient start point
 * shape.fillRadialGradientStartPoint({
 *   x: 20,
 *   y: 10
 * });
 */

Factory.addGetterSetter(Shape, 'fillRadialGradientStartPointX', 0);
/**
 * get/set fill radial gradient start point x
 * @name Konva.Shape#fillRadialGradientStartPointX
 * @method
 * @param {Number} x
 * @returns {Number}
 * @example
 * // get fill radial gradient start point x
 * var startPointX = shape.fillRadialGradientStartPointX();
 *
 * // set fill radial gradient start point x
 * shape.fillRadialGradientStartPointX(20);
 */

Factory.addGetterSetter(Shape, 'fillRadialGradientStartPointY', 0);
/**
 * get/set fill radial gradient start point y
 * @name Konva.Shape#fillRadialGradientStartPointY
 * @method
 * @param {Number} y
 * @returns {Number}
 * @example
 * // get fill radial gradient start point y
 * var startPointY = shape.fillRadialGradientStartPointY();
 *
 * // set fill radial gradient start point y
 * shape.fillRadialGradientStartPointY(20);
 */

Factory.addComponentsGetterSetter(Shape, 'fillRadialGradientEndPoint', [
  'x',
  'y',
]);

/**
 * get/set fill radial gradient end point
 * @name Konva.Shape#fillRadialGradientEndPoint
 * @method
 * @param {Object} endPoint
 * @param {Number} endPoint.x
 * @param {Number} endPoint.y
 * @returns {Object}
 * @example
 * // get fill radial gradient end point
 * var endPoint = shape.fillRadialGradientEndPoint();
 *
 * // set fill radial gradient end point
 * shape.fillRadialGradientEndPoint({
 *   x: 20,
 *   y: 10
 * });
 */

Factory.addGetterSetter(Shape, 'fillRadialGradientEndPointX', 0);
/**
 * get/set fill radial gradient end point x
 * @name Konva.Shape#fillRadialGradientEndPointX
 * @method
 * @param {Number} x
 * @returns {Number}
 * @example
 * // get fill radial gradient end point x
 * var endPointX = shape.fillRadialGradientEndPointX();
 *
 * // set fill radial gradient end point x
 * shape.fillRadialGradientEndPointX(20);
 */

Factory.addGetterSetter(Shape, 'fillRadialGradientEndPointY', 0);
/**
 * get/set fill radial gradient end point y
 * @name Konva.Shape#fillRadialGradientEndPointY
 * @method
 * @param {Number} y
 * @returns {Number}
 * @example
 * // get fill radial gradient end point y
 * var endPointY = shape.fillRadialGradientEndPointY();
 *
 * // set fill radial gradient end point y
 * shape.fillRadialGradientEndPointY(20);
 */

Factory.addGetterSetter(Shape, 'fillPatternRotation', 0);

/**
 * get/set fill pattern rotation in degrees
 * @name Konva.Shape#fillPatternRotation
 * @method
 * @param {Number} rotation
 * @returns {Konva.Shape}
 * @example
 * // get fill pattern rotation
 * var patternRotation = shape.fillPatternRotation();
 *
 * // set fill pattern rotation
 * shape.fillPatternRotation(20);
 */

Factory.backCompat(Shape, {
  dashArray: 'dash',
  getDashArray: 'getDash',
  setDashArray: 'getDash',

  drawFunc: 'sceneFunc',
  getDrawFunc: 'getSceneFunc',
  setDrawFunc: 'setSceneFunc',

  drawHitFunc: 'hitFunc',
  getDrawHitFunc: 'getHitFunc',
  setDrawHitFunc: 'setHitFunc',
});

Collection.mapMethods(Shape);
